#include <basic/errcode.h>
#include <basic/ossl.h>

#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rand.h>

struct _ossl_key
{/*{{{*/
    union
    {
        struct
        {
            unsigned int algm;
            const EVP_CIPHER *cipher;
            unsigned char *key;
            int key_bytes;
            unsigned char *iv;
            int iv_bytes;
        } sym;
        struct
        {
            RSA *key;
        } rsa;
    } u;
};/*}}}*/

static pthread_mutex_t *_lock_cs;
static long *_lock_count;

static unsigned long _pthreads_thread_id(void)
{/*{{{*/
	return((unsigned long)pthread_self());
}/*}}}*/

static void _pthreads_locking_callback(int mode, int type, char *file,
	int line)
{/*{{{*/
	if (mode & CRYPTO_LOCK)
	{
		pthread_mutex_lock(&_lock_cs[type]);
		_lock_count[type]++;
	}
	else
	{
		pthread_mutex_unlock(&_lock_cs[type]);
	}
}/*}}}*/

void ossl_init()
{/*{{{*/
    OpenSSL_add_all_algorithms();
}/*}}}*/

void ossl_init_mt()
{/*{{{*/
	int i;

    OpenSSL_add_all_algorithms();

	_lock_cs=OPENSSL_malloc(CRYPTO_num_locks()*
            sizeof(pthread_mutex_t));	
	_lock_count=OPENSSL_malloc(CRYPTO_num_locks()*sizeof(long));
	for(i=0; i<CRYPTO_num_locks(); i++)
	{
		_lock_count[i]=0;
		pthread_mutex_init(&_lock_cs[i], NULL);
	}
	CRYPTO_set_id_callback((unsigned long(*)())_pthreads_thread_id);
	CRYPTO_set_locking_callback((void (*)())_pthreads_locking_callback);
}/*}}}*/

void ossl_clean()
{/*{{{*/
    EVP_cleanup();
}/*}}}*/

void ossl_clean_mt()
{/*{{{*/
	int i;

	CRYPTO_set_locking_callback(NULL);
	for(i=0; i<CRYPTO_num_locks(); i++)
	{
		pthread_mutex_destroy(&_lock_cs[i]);
	}
	OPENSSL_free(_lock_cs);
	OPENSSL_free(_lock_count);

    EVP_cleanup();
}/*}}}*/

static const EVP_MD *_get_digest(unsigned int algm)
{/*{{{*/
    const EVP_MD *digest;

    switch(algm)
    {
        case OSSL_DIGEST_MD5: digest=EVP_md5(); break;
        case OSSL_DIGEST_SHA1: digest=EVP_sha1(); break;
#if (OPENSSL_VERSION_NUMBER >> 12) >= 0x00908L
        case OSSL_DIGEST_SHA224: digest=EVP_sha224(); break;
        case OSSL_DIGEST_SHA256: digest=EVP_sha256(); break;
        case OSSL_DIGEST_SHA384: digest=EVP_sha384(); break;
        case OSSL_DIGEST_SHA512: digest=EVP_sha512(); break;
#endif
        default: digest=NULL;
    }
    return(digest);
}/*}}}*/

int ossl_digest(unsigned int algm,
        unsigned char *data, int data_size,
        unsigned char *value, int *value_size)
{/*{{{*/
    EVP_MD_CTX ctx;
    const EVP_MD *digest;
    int ret;

    if(!data || data_size < 0 || !value || !value_size)
        return(RET_ERR_PARA);
    if((digest=_get_digest(algm)) == NULL)
        return(-1);

#if (OPENSSL_VERSION_NUMBER >> 12) <= 0x00906L
    EVP_DigestInit(&ctx, digest);
    EVP_DigestUpdate(&ctx, data, data_size);
    EVP_DigestFinal(&ctx, value, (unsigned int *)value_size);
    ret=0;
#else
    EVP_MD_CTX_init(&ctx);
    if(EVP_DigestInit(&ctx, digest))
        if(EVP_DigestUpdate(&ctx, data, data_size))
            if(EVP_DigestFinal(&ctx, value, (unsigned int *)value_size))
                ret=0;
            else
                ret=-4;
        else
            ret=-3;
    else
        ret=-2;
    EVP_MD_CTX_cleanup(&ctx);
#endif

    return(ret);
}/*}}}*/

int ossl_digest_file(unsigned int algm, char *filename,
        unsigned char *value, int *value_size)
{/*{{{*/
    const EVP_MD *digest;
    BIO *bio_file, *bio_digest;
    int ret;

    if(!filename || !value || !value_size)
        return(RET_ERR_PARA);
    if((digest=_get_digest(algm)) == NULL)
        return(-1);

    if((bio_file=BIO_new_file(filename, "rb")))
    {
        if((bio_digest=BIO_new(BIO_f_md())))
        {
            if(BIO_set_md(bio_digest, digest))
            {
                char buf[KB(4)];
                int rdlen;

                BIO_push(bio_digest, bio_file);
                while(BIO_read(bio_digest, buf, sizeof(buf)) > 0);
                if((rdlen=BIO_gets(bio_digest, buf, sizeof(buf))) > 0)
                {
                    memcpy(value, buf, rdlen);
                    *value_size=rdlen;
                    ret=0;
                }
                else ret=-5;
                BIO_pop(bio_digest);
            }
            else ret=-4;
            BIO_free(bio_digest);
        }
        else ret=-3;
        BIO_free(bio_file);
    }
    else ret=-2;

    return(ret);
}/*}}}*/

int base64_wrap(char *inp, char *outp)
{/*{{{*/
    static int BASE64_WRAP=64; 

    if(!inp || !outp)
        return(RET_ERR_PARA);
    int inlen=strlen(inp);
    if(inlen <= 0)
        return(RET_ERR_PARA);

    int wrap=inlen/BASE64_WRAP;
    if(inlen % BASE64_WRAP) ++wrap;
    char *tmp;
    ALLOC_MULTI_RETERR(tmp, char, inlen+wrap*2+10, RET_ERR_ALLOC);
    char *from=inp;
    char *to=tmp;
    int len;
    int i;
    for(i=0; i<wrap; ++i)
    {
        if(i == wrap-1)
            len=inlen-i*BASE64_WRAP;
        else
            len=BASE64_WRAP;
        strncpy(to, from, len);
        from+=len;
        to+=len;
        strcpy(to, RTNS);
        to+=strlen(RTNS);
    }
    strcpy(outp, tmp); 
    free(tmp);

    return(0);
}/*}}}*/

int base64_unwrap(char *inp, char *outp)
{/*{{{*/
    if(!inp || !outp)
        return(RET_ERR_PARA);
    int inlen=strlen(inp);
    if(inlen <= 0)
        return(RET_ERR_PARA);

    char *tmp;
    ALLOC_MULTI_RETERR(tmp, char, inlen+1, RET_ERR_ALLOC);
    char *from=inp;
    char *to=tmp;
    char *p;
    int len;
    while((p=strstr(from, RTNS)))
    {
        len=p-from;
        strncpy(to, from, len);
        from+=len+strlen(RTNS);
        to+=len;
    }
    strcpy(to, from);
    strcpy(outp, tmp);
    free(tmp);

    return(0);
}/*}}}*/

static BIO_METHOD *_get_method(unsigned int algm)
{/*{{{*/
    BIO_METHOD *method;

    switch(algm)
    {
        case OSSL_ENCODE_BASE64: method=BIO_f_base64(); break;
        default: method=NULL; break;
    }
    return(method);
}/*}}}*/

int ossl_encode(unsigned int algm,
        unsigned char *plain, int plain_size,
        char *enc, int *p_enc_size)
{/*{{{*/
    BIO_METHOD *method;
    BIO *bio_enc, *bio_algm;
    int ret;

    if(!plain || plain_size <= 0 || !enc || !p_enc_size)
        return(RET_ERR_PARA);
    if((method=_get_method(algm)) == NULL)
        return(-1);

    if((bio_enc=BIO_new(BIO_s_mem())))
    {
        if((bio_algm=BIO_new(method)))
        {
            unsigned char *p;
            int res;
            BIO_push(bio_algm, bio_enc);
            p=plain;
            while((res=BIO_write(bio_algm, p, plain_size)) > 0 &&
                    (plain_size-=res) > 0)
            {   p+=res;   }
            if(res > 0)
            {
                res=BIO_flush(bio_algm);
                if((res=BIO_get_mem_data(bio_algm, &p)) > 0)
                {
                    memcpy(enc, p, res);
                    enc[res]=ZERO;
                    *p_enc_size=res;
                    ret=0;
                }
                else
                    ret=-5;
                BIO_pop(bio_algm);
            }
            else
                ret=-4;
            BIO_free(bio_algm);
        }
        else
            ret=-3;
        BIO_free(bio_enc);
    }
    else ret=-2;

    return(ret);
}/*}}}*/

int ossl_encode_file(unsigned int algm, char *plain_file,
        char *enc, int *p_enc_size)
{/*{{{*/
    int ret=0;
    BIO_METHOD *method;
    BIO *bio_plain, *bio_enc, *bio_algm;

    if(!plain_file || !enc || !p_enc_size)
        return(RET_ERR_PARA);
    if((method=_get_method(algm)) == NULL)
        return(-1);

    if((bio_plain=BIO_new_file(plain_file, "rb")))
    {
        if((bio_enc=BIO_new(BIO_s_mem())))
        {
            if((bio_algm=BIO_new(method)))
            {
                char inbuf[BUFFER_SIZE];
                int res;
                char *p;
                BIO_push(bio_algm, bio_enc);
                while((res=BIO_read(bio_plain, inbuf, sizeof(inbuf)))
                        > 0)
                    BIO_write(bio_algm, inbuf, res);
                res=BIO_flush(bio_algm);
                if((res=BIO_get_mem_data(bio_algm, &p)) > 0)
                {
                    memcpy(enc, p, res);
                    enc[res]=ZERO;
                    *p_enc_size=res;
                }
                else
                    ret=-5;
                BIO_pop(bio_algm);
                BIO_free(bio_algm);
            }
            else
                ret=-4;
            BIO_free(bio_enc);
        }
        else
            ret=-3;
        BIO_free(bio_plain);
    }
    else
        ret=-2;

    return(ret);
}/*}}}*/

int ossl_encode_file_to_file(unsigned int algm, char *plain_file,
        char *enc_file)
{/*{{{*/
    int ret=0;
    BIO_METHOD *method;
    BIO *bio_plain, *bio_enc, *bio_algm;

    if(!plain_file || !enc_file)
        return(RET_ERR_PARA);
    if((method=_get_method(algm)) == NULL)
        return(-1);

    if((bio_plain=BIO_new_file(plain_file, "rb")))
    {
        if((bio_enc=BIO_new_file(enc_file, "wb")))
        {
            if((bio_algm=BIO_new(method)))
            {
                char inbuf[BUFFER_SIZE];
                int res;
                BIO_push(bio_algm, bio_enc);
                while((res=BIO_read(bio_plain, inbuf, sizeof(inbuf)))
                        > 0)
                    BIO_write(bio_algm, inbuf, res);
                res=BIO_flush(bio_algm);
                BIO_pop(bio_algm);
                BIO_free(bio_algm);
            }
            else
                ret=-4;
            BIO_free(bio_enc);
        }
        else
            ret=-3;
        BIO_free(bio_plain);
    }
    else
        ret=-2;

    return(ret);
}/*}}}*/

int ossl_decode(unsigned int algm, char *enc, int enc_size,
        unsigned char *plain, int *p_plain_size)
{/*{{{*/
    BIO_METHOD *method;
    BIO *bio_enc, *bio_algm;
    int ret;

    if(!enc || enc_size <= 0 || !plain || !p_plain_size)
        return(RET_ERR_PARA);
    if((method=_get_method(algm)) == NULL)
        return(-1);

    if((bio_enc=BIO_new_mem_buf(enc, enc_size)))
    {
        if((bio_algm=BIO_new(method)))
        {
            unsigned char *p;
            int res;
            int plain_size=0;
            BIO_push(bio_algm, bio_enc);
            p=plain;
            while((res=BIO_read(bio_algm, p, BUFFER_SIZE)) > 0)
            {   p+=res; plain_size+=res;   }
            plain[plain_size]=ZERO;
            *p_plain_size=plain_size;
//            res=-1;
//            while(!BIO_eof(bio_algm))
//            {
//                if((res=BIO_read(bio_algm, p, BUFFER_SIZE)) <= 0)
//                    break;
//                p+=res; plain_size+=res;
//            }
//            if(res > 0)
//            {
//                plain[plain_size]=ZERO;
//                *p_plain_size=plain_size;
//                ret=0;
//            }
//            else
//                ret=-4;
            BIO_pop(bio_algm);
            BIO_free(bio_algm);
        }
        else
            ret=-3;
        BIO_free(bio_enc); 
    }
    else
        ret=-2;

    return(0);
}/*}}}*/

int ossl_decode_file(unsigned int algm, char *enc_file,
        unsigned char *plain, int *p_plain_size)
{/*{{{*/
    BIO_METHOD *method;
    BIO *bio_enc, *bio_algm;
    int ret;

    if(!enc_file || !plain || !p_plain_size)
        return(RET_ERR_PARA);
    if((method=_get_method(algm)) == NULL)
        return(-1);

    if((bio_enc=BIO_new_file(enc_file, "rb")))
    {
        if((bio_algm=BIO_new(method)))
        {
            unsigned char *p;
            int res;
            int plain_size=0;
            BIO_push(bio_algm, bio_enc);
            p=plain;
            while((res=BIO_read(bio_algm, p, BUFFER_SIZE)) > 0)
            {   p+=res; plain_size+=res;   }
            plain[plain_size]=ZERO;
            *p_plain_size=plain_size;
//            res=-1;
//            while(!BIO_eof(bio_algm))
//            {
//                if((res=BIO_read(bio_algm, p, BUFFER_SIZE)) <= 0)
//                    break;
//                p+=res; plain_size+=res;
//            }
//            if(res > 0)
//            {
//                plain[plain_size]=ZERO;
//                *p_plain_size=plain_size;
//                ret=0;
//            }
//            else
//                ret=-4;
            BIO_pop(bio_algm);
            BIO_free(bio_algm);
        }
        else
            ret=-3;
        BIO_free(bio_enc);
    }
    else
        ret=-2;

    return(0);
}/*}}}*/

int ossl_decode_file_to_file(unsigned int algm, char *enc_file,
        char *plain_file)
{/*{{{*/
    BIO_METHOD *method;
    BIO *bio_enc, *bio_algm, *bio_plain;
    int ret;

    if(!enc_file || !plain_file)
        return(RET_ERR_PARA);
    if((method=_get_method(algm)) == NULL)
        return(-1);

    if((bio_enc=BIO_new_file(enc_file, "rb")))
    {
        if((bio_plain=BIO_new_file(plain_file, "wb")))
        {
            if((bio_algm=BIO_new(method)))
            {
                char inbuf[BUFFER_SIZE];
                int res;
                BIO_push(bio_algm, bio_enc);
                while((res=BIO_read(bio_algm, inbuf, sizeof(inbuf)))
                        > 0)
                    BIO_write(bio_plain, inbuf, res);
                res=BIO_flush(bio_plain);
                BIO_pop(bio_algm);
                BIO_free(bio_algm);
            }
            else
                ret=-4;
            BIO_free(bio_plain);
        }
        else
            ret=-3;
        BIO_free(bio_enc);
    }
    else
        ret=-2;

    return(0);
}/*}}}*/

static int PADDING_UNIT_SIZE=7;
static int PADDING_SIZE=1;

static char CHARBASE[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

int random_padding(unsigned char *src, int src_size,
        unsigned char *dest, int *dest_size)
{/*{{{*/
    if(!src || src_size < 0 || !dest || !dest_size)
        return(RET_ERR_PARA);

    int i;
    unsigned int seed=0;
    if(src_size == 0)
    {
        for(i=0; i<PADDING_SIZE; ++i)
            dest[i]=CHARBASE[random_int(0, strlen(CHARBASE)-1, &seed)];
        dest[PADDING_SIZE]=ZERO;
        *dest_size=PADDING_SIZE;
        return(0);
    }

    unsigned char *mid;
    ALLOC_MULTI_RETERR(mid, unsigned char, src_size*2, RET_ERR_ALLOC);
    int src_offset;
    int mid_offset=0;
    int unit_size;
    for(src_offset=0; src_offset<src_size; src_offset+=PADDING_UNIT_SIZE)
    {
        unit_size=src_size-src_offset;
        if(unit_size > PADDING_UNIT_SIZE)
            unit_size=PADDING_UNIT_SIZE;
        memcpy(mid+mid_offset, src+src_offset, unit_size);
        mid_offset+=unit_size;
        for(i=0; i<PADDING_SIZE; ++i)
            mid[mid_offset+i]=
                CHARBASE[random_int(0, strlen(CHARBASE)-1, &seed)];
        mid_offset+=PADDING_SIZE;
    }
    memcpy(dest, mid, mid_offset); 
    dest[mid_offset]=ZERO;
    *dest_size=mid_offset;
    free(mid);

    return(0);
}/*}}}*/

int random_unpadding(unsigned char *src, int src_size,
        unsigned char *dest, int *dest_size)
{/*{{{*/
    if(!src || src_size < PADDING_SIZE || !dest || !dest_size)
        return(RET_ERR_PARA);

    unsigned char *mid;
    ALLOC_MULTI_RETERR(mid, unsigned char, src_size, RET_ERR_ALLOC);
    int src_offset;
    int mid_offset=0;
    int unit_size;
    for(src_offset=0; src_offset<src_size;
            src_offset+=PADDING_UNIT_SIZE+PADDING_SIZE)
    {
        unit_size=src_size-src_offset-PADDING_SIZE;
        if(unit_size > PADDING_UNIT_SIZE)
            unit_size=PADDING_UNIT_SIZE;
        if(unit_size < 0)
        {
            free(mid);
            return(RET_ERR_PARA);
        }
        memcpy(mid+mid_offset, src+src_offset, unit_size);
        mid_offset+=unit_size;
    }
    memcpy(dest, mid, mid_offset);
    dest[mid_offset]=ZERO;
    *dest_size=mid_offset;
    free(mid);

    return(0);
}/*}}}*/

static const EVP_CIPHER *_get_sym_cipher(unsigned int algm,
        int *p_key_bytes, int *p_iv_bytes)
{/*{{{*/
    const EVP_CIPHER *cipher;

    switch(algm)
    {
        case OSSL_SYM_DES_CBC:
            cipher=EVP_des_cbc();
            *p_key_bytes=64/BIT_OF_BYTE; *p_iv_bytes=64/BIT_OF_BYTE;
            break;
        case OSSL_SYM_DES_ECB:
            cipher=EVP_des_ecb();
            *p_key_bytes=64/BIT_OF_BYTE; *p_iv_bytes=64/BIT_OF_BYTE;
            break;
        case OSSL_SYM_DES_EDE_CBC:
            cipher=EVP_des_ede_cbc();
            *p_key_bytes=128/BIT_OF_BYTE; *p_iv_bytes=64/BIT_OF_BYTE;
            break;
        case OSSL_SYM_DES_EDE3_CBC:
            cipher=EVP_des_ede3_cbc();
            *p_key_bytes=192/BIT_OF_BYTE; *p_iv_bytes=64/BIT_OF_BYTE;
            break;
#if (OPENSSL_VERSION_NUMBER >> 12) >= 0x00907L
        case OSSL_SYM_DES_EDE_ECB:
            cipher=EVP_des_ede_ecb();
            *p_key_bytes=128/BIT_OF_BYTE; *p_iv_bytes=64/BIT_OF_BYTE;
            break;
        case OSSL_SYM_DES_EDE3_ECB:
            cipher=EVP_des_ede3_ecb();
            *p_key_bytes=192/BIT_OF_BYTE; *p_iv_bytes=64/BIT_OF_BYTE;
            break;
        case OSSL_SYM_AES_128_CBC:
            cipher=EVP_aes_128_cbc();
            *p_key_bytes=128/BIT_OF_BYTE; *p_iv_bytes=128/BIT_OF_BYTE;
            break;
        case OSSL_SYM_AES_128_ECB:
            cipher=EVP_aes_128_ecb();
            *p_key_bytes=128/BIT_OF_BYTE; *p_iv_bytes=128/BIT_OF_BYTE;
            break;
        case OSSL_SYM_AES_192_CBC:
            cipher=EVP_aes_192_cbc();
            *p_key_bytes=192/BIT_OF_BYTE; *p_iv_bytes=128/BIT_OF_BYTE;
            break;
        case OSSL_SYM_AES_192_ECB:
            cipher=EVP_aes_192_ecb();
            *p_key_bytes=192/BIT_OF_BYTE; *p_iv_bytes=128/BIT_OF_BYTE;
            break;
        case OSSL_SYM_AES_256_CBC:
            cipher=EVP_aes_256_cbc();
            *p_key_bytes=256/BIT_OF_BYTE; *p_iv_bytes=128/BIT_OF_BYTE;
            break;
        case OSSL_SYM_AES_256_ECB:
            cipher=EVP_aes_256_ecb();
            *p_key_bytes=256/BIT_OF_BYTE; *p_iv_bytes=128/BIT_OF_BYTE;
            break;
#endif
        default: cipher=NULL;
    }
    return(cipher);
}/*}}}*/

int ossl_sym_load_key(unsigned int algm, unsigned int form, char *text,
        OSSL_KEY *p_key)
{/*{{{*/
    const EVP_CIPHER *cipher;
    int key_bytes=0, iv_bytes=0;
    OSSL_KEY key;
    int len;
    unsigned char ekey[EVP_MAX_KEY_LENGTH];
    unsigned char eiv[EVP_MAX_IV_LENGTH];

    if(!p_key)
        return(RET_ERR_PARA);
    *p_key=NULL;
    if(!text)
        return(RET_ERR_PARA);

    if((cipher=_get_sym_cipher(algm, &key_bytes, &iv_bytes)) == NULL)
        return(-1);

    ALLOC_RETERR(key, struct _ossl_key, RET_ERR_ALLOC);
    key->u.sym.algm=algm;
    key->u.sym.cipher=cipher;
    switch(form)
    {
        case OSSL_SYM_KEY:
            len=strlen(text);
            if(key_bytes*2 != len)
            {   free(key); return(-3);   }
            ALLOC_MULTI_DOERR(key->u.sym.key, unsigned char, key_bytes+1,
                    free(key); return(RET_ERR_ALLOC));
            collapse_from_str(text, len, key->u.sym.key);
            key->u.sym.key_bytes=key_bytes;
            key->u.sym.iv=NULL;
            key->u.sym.iv_bytes=iv_bytes;
            break;

        case OSSL_SYM_PASSWD:
            EVP_BytesToKey(cipher, EVP_md5(), NULL,
                    (unsigned char *)text, strlen(text), 1, ekey, eiv);
            ALLOC_MULTI_DOERR(key->u.sym.key, unsigned char, key_bytes+1,
                    free(key); return(RET_ERR_ALLOC));
            memcpy(key->u.sym.key, ekey, key_bytes);
            key->u.sym.key_bytes=key_bytes;
            ALLOC_MULTI_DOERR(key->u.sym.iv, unsigned char, iv_bytes+1,
                    free(key->u.sym.key); free(key);
                    return(RET_ERR_ALLOC));
            memcpy(key->u.sym.iv, eiv, iv_bytes);
            key->u.sym.iv_bytes=iv_bytes;
            break;

        default:
            free(key);
            return(-2);
    }

    *p_key=key;
    return(0);
}/*}}}*/

void ossl_sym_free_key(OSSL_KEY key)
{/*{{{*/
    if(key)
    {
        FREE(key->u.sym.key);
        free(key);
    }
}/*}}}*/

int ossl_sym_get_key(OSSL_KEY key, char *key_txt, char *iv_txt)
{/*{{{*/
    if(!key_txt)
        return(RET_ERR_PARA);
    CLR_BUF(key_txt);
    if(!iv_txt)
        return(RET_ERR_PARA);
    CLR_BUF(iv_txt);
    if(!key)
        return(RET_ERR_PARA);

    expand_to_str(key->u.sym.key, key->u.sym.key_bytes, key_txt);
    if(key->u.sym.iv)
    {   expand_to_str(key->u.sym.iv, key->u.sym.iv_bytes, iv_txt);   }
    else
    {
        memset(iv_txt, CHAR0, key->u.sym.iv_bytes*2);
        iv_txt[key->u.sym.iv_bytes*2]=ZERO;
    }

    return(0);
}/*}}}*/

int ossl_sym_encrypt(OSSL_KEY key,
        unsigned char *plain, int plain_size,
        unsigned char *enc, int *p_enc_size)
{/*{{{*/
    int ret;
    EVP_CIPHER_CTX ctx;
    int update_size, final_size;

    if(!enc)
        return(RET_ERR_PARA);
    CLR_BUF(enc);
    if(!p_enc_size)
        return(RET_ERR_PARA);
    *p_enc_size=0;
    if(!key || !plain ||
            plain_size <= 0 || plain_size > OSSL_PLAIN_MAX_SIZE)
        return(RET_ERR_PARA);

    EVP_CIPHER_CTX_init(&ctx);
#if (OPENSSL_VERSION_NUMBER >> 12) <= 0x00906L
    if(EVP_EncryptInit(&ctx, key->u.sym.cipher,
                key->u.sym.key, key->u.sym.iv))
    {
        if(EVP_EncryptUpdate(&ctx, enc, &update_size,
                    plain, plain_size))
        {
            if(EVP_EncryptFinal(&ctx, enc+update_size, &final_size))
            {
                *p_enc_size=update_size+final_size;
                enc[*p_enc_size]=ZERO;
                ret=0;
            }
            else ret=-3;
        }
        else ret=-2;
    }
    else ret=-1;
#else
    if(EVP_EncryptInit_ex(&ctx, key->u.sym.cipher, NULL,
                key->u.sym.key, key->u.sym.iv))
    {
        if(EVP_EncryptUpdate(&ctx, enc, &update_size,
                    plain, plain_size))
        {
            if(EVP_EncryptFinal_ex(&ctx, enc+update_size,
                        &final_size))
            {
                *p_enc_size=update_size+final_size;
                enc[*p_enc_size]=ZERO;
                ret=0;
            }
            else ret=-3;
        }
        else ret=-2;
    }
    else ret=-1;
#endif
    EVP_CIPHER_CTX_cleanup(&ctx);

    return(ret);
}/*}}}*/

int ossl_sym_decrypt(OSSL_KEY key,
        unsigned char *enc, int enc_size,
        unsigned char *plain, int *p_plain_size)
{/*{{{*/
    int ret;
    EVP_CIPHER_CTX ctx;
    int update_size, final_size;

    if(!plain)
        return(RET_ERR_PARA);
    CLR_BUF(plain);
    if(!p_plain_size)
        return(RET_ERR_PARA);
    *p_plain_size=0;
    if(!key || !enc || enc_size <= 0)
        return(RET_ERR_PARA);

    EVP_CIPHER_CTX_init(&ctx);
#if (OPENSSL_VERSION_NUMBER >> 12) <= 0x00906L
    if(EVP_DecryptInit(&ctx, key->u.sym.cipher,
                key->u.sym.key, key->u.sym.iv))
    {
        if(EVP_DecryptUpdate(&ctx, plain, &update_size,
                    enc, enc_size))
        {
            if(EVP_DecryptFinal(&ctx, plain+update_size, &final_size))
            {
                *p_plain_size=update_size+final_size;
                plain[*p_plain_size]=ZERO;
                ret=0;
            }
            else ret=-3;
        }
        else ret=-2;
    }
    else ret=-1;
#else
    if(EVP_DecryptInit_ex(&ctx, key->u.sym.cipher, NULL,
                key->u.sym.key, key->u.sym.iv))
    {
        if(EVP_DecryptUpdate(&ctx, plain, &update_size,
                    enc, enc_size))
        {
            if(EVP_DecryptFinal_ex(&ctx, plain+update_size,
                        &final_size))
            {
                *p_plain_size=update_size+final_size;
                plain[*p_plain_size]=ZERO;
                ret=0;
            }
            else ret=-3;
        }
        else ret=-2;
    }
    else ret=-1;
#endif
    EVP_CIPHER_CTX_cleanup(&ctx);

    return(ret);
}/*}}}*/

#define _DEF_KEY_BITS       1024

int ossl_rsa_generate_key_pair(unsigned int key_bits,
        char *private_key_passwd,
        char *public_key_pem, char *private_key_pem)
{/*{{{*/
    RSA *key;
    BIO *pubout, *priout;
    unsigned char *ptr;
    int size;
    int ret=0;

    if(!public_key_pem)
        return(RET_ERR_PARA);
    CLR_BUF(public_key_pem);
    if(!private_key_pem)
        return(RET_ERR_PARA);
    CLR_BUF(private_key_pem);
    if(!private_key_passwd)
        return(RET_ERR_PARA);

    if(key_bits == 0)
        key_bits=_DEF_KEY_BITS;
    else
        if(key_bits % _DEF_KEY_BITS)
            key_bits+=_DEF_KEY_BITS-key_bits%_DEF_KEY_BITS;
    if((key=RSA_generate_key(key_bits, 0x10001, NULL, NULL)) == NULL)
        return(-1);

    if((pubout=BIO_new(BIO_s_mem())))
    {
        if(PEM_write_bio_RSA_PUBKEY(pubout, key))
        {
            if((size=BIO_get_mem_data(pubout, &ptr)) > 0)
            {
                memcpy(public_key_pem, ptr, size);
                public_key_pem[size]=ZERO;
            }
            else
            {   ret=-4; goto OUT_PUB;   }
        }
        else
        {   ret=-3; goto OUT_PUB;   }
    }
    else
    {   ret=-2; goto OUT_ERR;   }

    if((priout=BIO_new(BIO_s_mem())))
    {
        if((size=PEM_write_bio_RSAPrivateKey(priout, key,
                        EVP_des_ede3_cbc(),
                        (unsigned char *)private_key_passwd,
                        strlen(private_key_passwd),
                        NULL, NULL)) > 0)
        {
            if((size=BIO_get_mem_data(priout, &ptr)) > 0)
            {
                memcpy(private_key_pem, ptr, size);
                private_key_pem[size]=ZERO;
            }
            else
            {   ret=-7; goto OUT_PRI;   }
        }
        else
        {   ret=-6; goto OUT_PRI;   }
    }
    else
    {   ret=-5; goto OUT_PUB;   }

OUT_PRI:
    BIO_free(priout);
OUT_PUB:
    BIO_free(pubout);
OUT_ERR:
    RSA_free(key);
    return(ret);
}/*}}}*/

int ossl_rsa_generate_key_pair_file(unsigned int key_bits,
        char *private_key_passwd,
        char *public_key_pem_file, char *private_key_pem_file)
{/*{{{*/
    RSA *key;
    BIO *pubout, *priout;
    int size;
    int ret=0;

    if(!public_key_pem_file || !private_key_pem_file ||
            !private_key_passwd)
        return(RET_ERR_PARA);

    if(key_bits == 0)
        key_bits=_DEF_KEY_BITS;
    else
        if(key_bits % _DEF_KEY_BITS)
            key_bits+=_DEF_KEY_BITS-key_bits%_DEF_KEY_BITS;
    if((key=RSA_generate_key(key_bits, 0x10001, NULL, NULL)) == NULL)
        return(-1);

    if((pubout=BIO_new_file(public_key_pem_file, "wb")))
    {
        if(PEM_write_bio_RSA_PUBKEY(pubout, key) == 0)
        {   ret=-3; goto OUT_PUB;   }
    }
    else
    {   ret=-2; goto OUT_ERR;   }

    if((priout=BIO_new_file(private_key_pem_file, "wb")))
    {
        if((size=PEM_write_bio_RSAPrivateKey(priout, key,
                        EVP_des_ede3_cbc(),
                        (unsigned char *)private_key_passwd,
                        strlen(private_key_passwd),
                        NULL, NULL)) ==  0)
        {   ret=-5; goto OUT_PRI;   }
    }
    else
    {   ret=-4; goto OUT_PUB;   }

OUT_PRI:
    BIO_free(priout);
OUT_PUB:
    BIO_free(pubout);
OUT_ERR:
    RSA_free(key);
    return(ret);
}/*}}}*/

int ossl_rsa_load_public_key(char *key_pem, OSSL_KEY *pkey)
{/*{{{*/
    int ret;
    BIO *in;
    OSSL_KEY key;

    if(!pkey)
        return(RET_ERR_PARA);
    *pkey=NULL;
    if(!key_pem)
        return(RET_ERR_PARA);

    ALLOC_RETERR(key, struct _ossl_key, RET_ERR_ALLOC);
    key->u.rsa.key=NULL;
    if((in=BIO_new_mem_buf(key_pem, -1)))
    {
        if((key->u.rsa.key=PEM_read_bio_RSA_PUBKEY(in, NULL,
                        NULL, NULL)))
            ret=0;
        else
            ret=-2;
        BIO_free(in);
    }
    else
        ret=-1;

    if(ret)
        free(key);
    else
        *pkey=key;
    return(ret);
}/*}}}*/

int ossl_rsa_load_public_key_file(char *key_pem_file, OSSL_KEY *pkey)
{/*{{{*/
    int ret;
    BIO *in;
    OSSL_KEY key;

    if(!pkey)
        return(RET_ERR_PARA);
    *pkey=NULL;
    if(!key_pem_file)
        return(RET_ERR_PARA);

    ALLOC_RETERR(key, struct _ossl_key, RET_ERR_ALLOC);
    key->u.rsa.key=NULL;
    if((in=BIO_new(BIO_s_file())))
    {
        if(BIO_read_filename(in, key_pem_file) > 0)
        {
            if((key->u.rsa.key=PEM_read_bio_RSA_PUBKEY(in, NULL,
                            NULL, NULL)))
                ret=0;
            else
                ret=-3;
        }
        else
            ret=-2;
        BIO_free(in);
    }
    else
        ret=-1;

    if(ret)
        free(key);
    else
        *pkey=key;
    return(ret);
}/*}}}*/

int ossl_rsa_load_private_key(char *key_pem, char *passwd,
        OSSL_KEY *pkey)
{/*{{{*/
    int ret;
    BIO *in;
    OSSL_KEY key;

    if(!pkey)
        return(RET_ERR_PARA);
    *pkey=NULL;
    if(!key_pem)
        return(RET_ERR_PARA);

    ALLOC_RETERR(key, struct _ossl_key, RET_ERR_ALLOC);
    key->u.rsa.key=NULL;
    if((in=BIO_new_mem_buf(key_pem, -1)))
    {
        if((key->u.rsa.key=PEM_read_bio_RSAPrivateKey(in, NULL,
                        NULL, passwd)))
            ret=0;
        else
            ret=-2;
        BIO_free(in);
    }
    else
        ret=-1;

    if(ret)
        free(key);
    else
        *pkey=key;
    return(ret);
}/*}}}*/

int ossl_rsa_load_private_key_file(char *key_pem_file, char *passwd,
        OSSL_KEY *pkey)
{/*{{{*/
    int ret;
    BIO *in;
    OSSL_KEY key;

    if(!pkey)
        return(RET_ERR_PARA);
    *pkey=NULL;
    if(!key_pem_file)
        return(RET_ERR_PARA);

    ALLOC_RETERR(key, struct _ossl_key, RET_ERR_ALLOC);
    key->u.rsa.key=NULL;
    if((in=BIO_new(BIO_s_file())))
    {
        if(BIO_read_filename(in, key_pem_file) > 0)
        {
            if((key->u.rsa.key=PEM_read_bio_RSAPrivateKey(in, NULL,
                            NULL, passwd)))
                ret=0;
            else
                ret=-3;
        }
        else
            ret=-2;
        BIO_free(in);
    }
    else
        ret=-1;

    if(ret)
        free(key);
    else
        *pkey=key;
    return(ret);
}/*}}}*/

void ossl_rsa_free_key(OSSL_KEY key)
{/*{{{*/
    if(key)
    {
        RSA_free(key->u.rsa.key);
        free(key);
    }
}/*}}}*/

int ossl_rsa_encrypt(OSSL_KEY public_key,
        unsigned char *plain, int plain_size,
        unsigned char *enc, int *p_enc_size)
{/*{{{*/
    int INSIZE;
    int plain_offset;
    int insize;
    int enc_offset;
    int outsize;

    if(!enc)
        return(RET_ERR_PARA);
    CLR_BUF(enc);
    if(!p_enc_size)
        return(RET_ERR_PARA);
    *p_enc_size=0;
    if(!public_key || !plain || plain_size <= 0)
        return(RET_ERR_PARA);

    INSIZE=RSA_size(public_key->u.rsa.key)-RSA_PKCS1_PADDING_SIZE; 
    plain_offset=0;
    enc_offset=0;
    while((insize=plain_size-plain_offset) > 0)
    {
        if(insize > INSIZE) insize=INSIZE;
        outsize=RSA_public_encrypt(insize, plain+plain_offset,
                enc+enc_offset,
                public_key->u.rsa.key, RSA_PKCS1_PADDING);
        if(outsize < 0) return(-1);
        //fprintf(stderr, "%s: insize(%d) outsize(%d)\n",
        //        __func__, insize, outsize);
        enc_offset+=outsize;
        plain_offset+=insize;
    }
    enc[enc_offset]=ZERO;
    *p_enc_size=enc_offset;
    return(0);
}/*}}}*/

int ossl_rsa_decrypt(OSSL_KEY private_key,
        unsigned char *enc, int enc_size,
        unsigned char *plain, int *p_plain_size)
{/*{{{*/
    int INSIZE;
    int enc_offset;
    int plain_offset;
    int outsize;

    if(!plain)
        return(RET_ERR_PARA);
    CLR_BUF(plain);
    if(!p_plain_size)
        return(RET_ERR_PARA);
    *p_plain_size=0;
    if(!private_key)
        return(RET_ERR_PARA);
    INSIZE=RSA_size(private_key->u.rsa.key); 
    if(!enc || enc_size <= 0 || enc_size % INSIZE)
        return(RET_ERR_PARA);

    plain_offset=0;
    for(enc_offset=0; enc_offset<enc_size; enc_offset+=INSIZE)
    {
        outsize=RSA_private_decrypt(INSIZE, enc+enc_offset,
                plain+plain_offset,
                private_key->u.rsa.key, RSA_PKCS1_PADDING);
        if(outsize < 0) return(-1);
        //fprintf(stderr, "%s: outsize(%d)\n",
        //        __func__, outsize);
        plain_offset+=outsize;
    }
    plain[plain_offset]=ZERO;
    *p_plain_size=plain_offset;
    return(0);
}/*}}}*/

int ossl_rsa_sign(OSSL_KEY private_key,
        unsigned char *data, int data_size,
        unsigned char *sign, int *p_sign_size)
{/*{{{*/
    unsigned char m[512];
    int m_size;

    if(!sign)
        return(RET_ERR_PARA);
    CLR_BUF(sign);
    if(!p_sign_size)
        return(RET_ERR_PARA);
    *p_sign_size=0;
    if(!private_key || !data || data_size < 0)
        return(RET_ERR_PARA);

    if(ossl_digest(OSSL_DIGEST_SHA1, data, data_size, m, &m_size))
        return(-1);
    //fprintf(stderr, "%s: m_size(%d)\n", __func__, m_size);
    if(RSA_sign(NID_sha1, m, (unsigned int)m_size,
                sign, (unsigned int *)p_sign_size,
                private_key->u.rsa.key) == 0)
        return(-2);
    sign[*p_sign_size]=ZERO;
    return(0);
}/*}}}*/

int ossl_rsa_verify(OSSL_KEY public_key,
        unsigned char *data, int data_size,
        unsigned char *sign, int sign_size)
{/*{{{*/
    unsigned char m[512];
    int m_size;

    if(!public_key || !data || data_size < 0 ||
            !sign || sign_size < 0)
        return(RET_ERR_PARA);

    if(ossl_digest(OSSL_DIGEST_SHA1, data, data_size, m, &m_size))
        return(-1);
    if(RSA_verify(NID_sha1, m, (unsigned int)m_size,
                sign, (unsigned int)sign_size,
                public_key->u.rsa.key) == 0)
        return(-2);
    return(0);
}/*}}}*/

#if 0
int secure_encipher(SECURE_PKEY public_key,
    char *plain_data, int plain_size,
    BYTE *cipher_data, int *cipher_size)
{/*{{{*/
    int ret=-9999;
    EVP_CIPHER_CTX cipher_ctx;
    EVP_CIPHER *cipher;
    BYTE iv[EVP_MAX_IV_LENGTH];
    BYTE *ekey=NULL;
    int ekeylen;
    BYTE *edata=NULL;
    int edatalen;
    int i;
    int randata[128];
    struct timeval time_val;
    UINT rseed;
    int outlen;
    int net_ekeylen, net_edatalen;
    int real_cipher_size;

    if(!public_key || !plain_data || plain_size<=0 ||
       !cipher_data || !cipher_size)
    {
        return(-1);
    }

    if(!(cipher=(EVP_CIPHER *)EVP_get_cipherbyname(
           default_cipher_name)))
    {
        return(-2);
    }
    ALLOC_MULTI(edata, BYTE, 2*EVP_CIPHER_block_size(cipher)+
        2*plain_size+32, OUT_ERR);
    ALLOC_MULTI(ekey, BYTE, EVP_PKEY_size(public_key->pkey)+32,
            OUT_ERR);

    gettimeofday(&time_val, NULL);
    rseed=time_val.tv_usec;
    for(i=0; i<128; i++)  randata[i]=rand_r(&rseed);
    RAND_seed(randata, sizeof(randata));
    RAND_bytes(iv, sizeof(iv));

    if(!EVP_SealInit(&cipher_ctx, cipher, &ekey, &ekeylen, iv,
           &public_key->pkey, 1))
    {
        ret=-3;
        goto OUT_ERR;
    }
    if(!EVP_SealUpdate(&cipher_ctx, edata, &outlen,
                (BYTE *)plain_data, plain_size))
    {
        ret=-4;
        goto OUT_CIPHER;
    }
    edatalen=outlen;
#if (OPENSSL_VERSION_NUMBER >> 12) == 0x00906L
    EVP_SealFinal(&cipher_ctx, edata+edatalen, &outlen);
#elif (OPENSSL_VERSION_NUMBER >> 12) == 0x00907L
    if(!EVP_SealFinal(&cipher_ctx, edata+edatalen, &outlen))
	{
		ret=-5;
		goto OUT_CIPHER;
	}
#endif
    edatalen+=outlen;
    real_cipher_size=sizeof(iv)+sizeof(ekeylen)+ekeylen+
        sizeof(edatalen)+edatalen;
    if(*cipher_size > 0 && *cipher_size < real_cipher_size)
    {
        ret=-6;
        goto OUT_CIPHER;
    }
    *cipher_size=real_cipher_size;
    net_ekeylen=htonl(ekeylen);
    net_edatalen=htonl(edatalen);
    memcpy(cipher_data, iv, sizeof(iv));
    memcpy(cipher_data+sizeof(iv), &net_ekeylen, sizeof(net_ekeylen));
    memcpy(cipher_data+sizeof(iv)+sizeof(net_ekeylen), ekey, ekeylen);
    memcpy(cipher_data+sizeof(iv)+sizeof(net_ekeylen)+ekeylen,
        &net_edatalen, sizeof(net_edatalen));
    memcpy(cipher_data+sizeof(iv)+sizeof(net_ekeylen)+ekeylen+
        sizeof(net_edatalen), edata, edatalen);

	EVP_CIPHER_CTX_cleanup(&cipher_ctx);
    FREE(ekey);
    FREE(edata);
    return(0);

OUT_CIPHER:
	EVP_CIPHER_CTX_cleanup(&cipher_ctx);

OUT_ERR:
    FREE(ekey);
    FREE(edata);
    return(ret);
}/*}}}*/

int secure_decipher(SECURE_PKEY private_key,
    BYTE *cipher_data, int cipher_size,
    char *plain_data, int *plain_size)
{/*{{{*/
	int ret=0;
    EVP_CIPHER_CTX cipher_ctx;
    EVP_CIPHER *cipher=NULL;
    BYTE *iv;
    BYTE *ekey;
    int ekeylen;
    BYTE *edata;
    int edatalen;
    int outlen;

    if(!private_key || !cipher_data || cipher_size<=sizeof(iv) ||
       !plain_data || !plain_size)
        return(-1);

    if(!(cipher=(EVP_CIPHER *)EVP_get_cipherbyname(
           default_cipher_name)))
        return(-2);

    iv=cipher_data;
    cipher_size-=EVP_MAX_IV_LENGTH;
    if(cipher_size<=sizeof(ekeylen))
        return(-3);
    memcpy(&ekeylen, iv+EVP_MAX_IV_LENGTH, sizeof(ekeylen));
    cipher_size-=sizeof(ekeylen);
    ekeylen=ntohl(ekeylen);
    if(ekeylen<=0 || cipher_size<=ekeylen)
        return(-4);
    ekey=iv+EVP_MAX_IV_LENGTH+sizeof(ekeylen);
    cipher_size-=ekeylen;
    if(cipher_size<=sizeof(edatalen))
        return(-5);
    memcpy(&edatalen, iv+EVP_MAX_IV_LENGTH+sizeof(ekeylen)+ekeylen,
        sizeof(edatalen));
    cipher_size-=sizeof(edatalen);
    edatalen=ntohl(edatalen);
    if(edatalen<=0 || cipher_size!=edatalen)
        return(-6);
    edata=iv+EVP_MAX_IV_LENGTH+sizeof(ekeylen)+ekeylen+
        sizeof(edatalen);
    if(*plain_size > 0 &&
            *plain_size<2*EVP_CIPHER_block_size(cipher)+edatalen)
        return(-7);

    if(!EVP_OpenInit(&cipher_ctx, cipher, ekey, ekeylen, iv,
           private_key->pkey))
	{
		ret=-8;
		goto OUT_ERR;
	}
    if(!EVP_OpenUpdate(&cipher_ctx, (BYTE *)plain_data, &outlen,
                edata, edatalen))
	{
        ret=-9;
		goto OUT_ERR;
	}
    *plain_size=outlen;
    if(!EVP_OpenFinal(&cipher_ctx, (BYTE *)plain_data+outlen,
                &outlen))
	{
        ret=-10;
		goto OUT_ERR;
	}
    (*plain_size)+=outlen;

OUT_ERR:
	EVP_CIPHER_CTX_cleanup(&cipher_ctx);
    return(ret);
}/*}}}*/

int secure_sign(SECURE_PKEY private_key, char *data, int data_size,
    BYTE *sign, int *sign_size)
{/*{{{*/
	int ret=0;
    EVP_MD_CTX md_ctx;
    const EVP_MD *md;

    if(!private_key || !data || data_size<=0 || !sign || !sign_size)
        return(-1);

    if(!(md=EVP_get_digestbyname(default_md_name)))
        return(-2);
#if (OPENSSL_VERSION_NUMBER >> 12) == 0x00906L
    EVP_SignInit(&md_ctx, md);
    EVP_SignUpdate(&md_ctx, data, data_size);
#elif (OPENSSL_VERSION_NUMBER >> 12) == 0x00907L
	EVP_MD_CTX_init(&md_ctx);
    if(!EVP_SignInit(&md_ctx, md))
	{
		ret=-3;
		goto OUT_ERR;
	}
    if(!EVP_SignUpdate(&md_ctx, data, data_size))
	{
		ret=-4;
		goto OUT_ERR;
	}
#endif
    if(!EVP_SignFinal(&md_ctx, sign, (UINT *)sign_size,
                private_key->pkey))
	{
		ret=-5;
		goto OUT_ERR;
	}

OUT_ERR:
#if (OPENSSL_VERSION_NUMBER >> 12) == 0x00907L
	EVP_MD_CTX_cleanup(&md_ctx);
#endif
    return(0);
}/*}}}*/

int secure_verify(SECURE_PKEY public_key, char *data, int data_size,
    BYTE *sign, int sign_size)
{/*{{{*/
	int ret=0;
    EVP_MD_CTX md_ctx;
    const EVP_MD *md;

    if(!public_key || !data || data_size<=0 || !sign || sign_size<=0)
        return(-1);

    if(!(md=EVP_get_digestbyname(default_md_name)))
        return(-2);
#if (OPENSSL_VERSION_NUMBER >> 12) == 0x00906L
    EVP_VerifyInit(&md_ctx, md);
    EVP_VerifyUpdate(&md_ctx, data, data_size);
#elif (OPENSSL_VERSION_NUMBER >> 12) == 0x00907L
	EVP_MD_CTX_init(&md_ctx);
    if(!EVP_VerifyInit(&md_ctx, md))
	{
		ret=-3;
		goto OUT_ERR;
	}
    if(!EVP_VerifyUpdate(&md_ctx, data, data_size))
	{
		ret=-4;
		goto OUT_ERR;
	}
#endif
    if(!EVP_VerifyFinal(&md_ctx, sign, sign_size, public_key->pkey))
	{
        ret=-5;
		goto OUT_ERR;
	}

OUT_ERR:
#if (OPENSSL_VERSION_NUMBER >> 12) == 0x00907L
	EVP_MD_CTX_cleanup(&md_ctx);
#endif
    return(ret);
}/*}}}*/
#endif
